﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Controls;
using SurfaceCardsBoardLibrary.Interfaces;
using Microsoft.Surface.Presentation.Controls;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Shapes;
using Microsoft.Surface.Presentation;

namespace SurfaceCardsBoardLibrary
{
    #region Events
    public delegate void ScatterViewItemManipulationEventHandler(object source, ScatterViewItemManipulationRoutedEventArgs e);
    public class ScatterViewItemManipulationRoutedEventArgs : RoutedEventArgs
    {
        private IEnumerable<ScatterViewItem> _collides;

        public ScatterViewItemManipulationRoutedEventArgs(RoutedEvent routedEvent)
            : this(routedEvent, null)
        { }

        public ScatterViewItemManipulationRoutedEventArgs(RoutedEvent routedEvent, object source)
            : base(routedEvent, source)
        { }

        public IEnumerable<ScatterViewItem> Collides
        {
            get { return _collides; }
            internal set
            {
                var collides = value;
                var deckcards = collides.FilterOnDeckCards();
                _collides = collides.Where(it => deckcards.Contains(it) == false);
            }
        }

        public IEnumerable<ScatterViewItem> Cards
        {
            get { return Collides.FilterOnCards(); }
        }

        public IEnumerable<ScatterViewItem> FreeCards
        {
            get { return Collides.FilterOnFreeCards(); }
        }

        public IEnumerable<ScatterViewItem> Decks
        {
            get { return Collides.FilterOnDecks(); }
        }

        public Point Center { get; set; }
    }

    public enum ManipulableObjectAction
    {
        CreateDeck,
        MergeDecks,
        RemoveDecks,
        AddCardsToDeck,
        RemoveCardsFromDeck,
        MoveCardsFromDeck,
        MoveCardOrDeckOnBoard,
    }

    public delegate void ManipulableObjectStateChangingEventHandler(object source, ManipulableObjectStateChangingEventArgs e);
    public class ManipulableObjectStateChangingEventArgs : RoutedEventArgs
    {
        public ManipulableObjectStateChangingEventArgs(RoutedEvent routedEvent)
            : this(routedEvent, null)
        { }

        public ManipulableObjectStateChangingEventArgs(RoutedEvent routedEvent, object source)
            : base(routedEvent, source)
        { }

        public List<ICard> Cards { get; set; }

        public List<IDeck> Decks { get; set; }

        public ManipulableObjectAction ManipulableObjectAction
        {
            get;
            internal set;
        }

        public IManipulableObject ManipulableSource { get; internal set; }

        public bool Cancel { get; set; }

        public Point Center
        {
            get;
            set;
        }
    }
    #endregion

    /// <summary></summary>
    public class Board : Grid
    {
        #region Members
        private BoardScatterView _boardScatterView;
        private ListCollectionView _cardCollectionView;
        private ListCollectionView _deckCollectionView;
        private Canvas _canvas;
        private Path collideValidationPath;
        #endregion

        #region Events
        public static readonly RoutedEvent ScatterViewItemManipulatingEvent =
            EventManager.RegisterRoutedEvent("ScatterViewItemManipulating", RoutingStrategy.Bubble, typeof(ScatterViewItemManipulationEventHandler), typeof(Board));

        public static readonly RoutedEvent ManipulableObjectStateChangingEvent =
            EventManager.RegisterRoutedEvent("ManipulableObjectStateChanging", RoutingStrategy.Bubble, typeof(ManipulableObjectStateChangingEventHandler), typeof(Board));

        public static readonly RoutedEvent ScatterViewItemManipulatedEvent =
            EventManager.RegisterRoutedEvent("ScatterViewItemManipulatedEvent", RoutingStrategy.Bubble, typeof(ScatterViewItemManipulationEventHandler), typeof(Board));
        #endregion

        public static readonly SolidColorBrush DefaultCollidePathColor = new SolidColorBrush(Color.FromArgb(200, 255, 255, 255));

        #region Constructors
        static Board()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Board), new FrameworkPropertyMetadata(typeof(Board)));
        }

        public Board()
            : base()
        {
            _canvas = new Canvas();
            this.Children.Add(_canvas);
            this._boardScatterView = new BoardScatterView();
            this.Children.Add(_boardScatterView);

            this.ScatterView.AddHandler(ScatterViewItem.ScatterManipulationCompletedEvent, new ScatterManipulationCompletedEventHandler(OnScatterManipulationCompleted));
            this.ScatterView.AddHandler(ScatterViewItem.ScatterManipulationDeltaEvent, new ScatterManipulationDeltaEventHandler(OnScatterManipulationDelta));
            this.ScatterView.AddHandler(ScatterViewItem.PreviewContactUpEvent, new ContactEventHandler(OnScatterViewItemPreviewContactUp));
            CollidePathBrush = DefaultCollidePathColor;
            IsCollidePathEnabled = true;
        }
        #endregion

        #region Manipulation and Validation

        private void OnScatterManipulationCompleted(object sender, ScatterManipulationCompletedEventArgs e)
        {
            if (e.Canceled)
                return;

            var svi_source = e.OriginalSource as ScatterViewItem;

            var collision = SurfaceHelper.GetOverridingScatterViewItemsVerbose(svi_source);

            var dcrea = new ScatterViewItemManipulationRoutedEventArgs(Board.ScatterViewItemManipulatedEvent)
            {
                Source = svi_source
            };

            if (collision.HasCollision)
            {
                var collides = collision.Collides;
                var deckcards = collides.FilterOnDeckCards();

                if (deckcards.Any())
                    collides = collides.Substract(deckcards);


                if (collides.Any())
                {
                    dcrea.Collides = collides.ToArray();
                    dcrea.Center = collision.Center;
                }
                else
                {
                    dcrea.Center = svi_source.ActualCenter;
                    dcrea.Collides = new ScatterViewItem[0];
                }
            }
            else
            {
                dcrea.Center = svi_source.ActualCenter;
                dcrea.Collides = new ScatterViewItem[0];
            }

            this.RaiseEvent(dcrea);
            RemoveCollideValidationPath();
        }

        private void OnScatterManipulationDelta(object sender, ScatterManipulationDeltaEventArgs e)
        {
            var svi_source = e.OriginalSource as ScatterViewItem;

            var collision = SurfaceHelper.GetOverridingScatterViewItemsVerbose(svi_source);

            var dcrea = new ScatterViewItemManipulationRoutedEventArgs(Board.ScatterViewItemManipulatingEvent);
            dcrea.Source = svi_source;

            if (collision.HasCollision)
            {
                var collides = collision.Collides;
                var deckcards = collides.FilterOnDeckCards();

                if (deckcards.Any())
                    collides = collides.Substract(deckcards);

                var validationGeometry = collision.Geometry;

                if (validationGeometry != Geometry.Empty)
                {
                    var sourceGeometry = SurfaceHelper.ComputeScatterViewGeometry(svi_source);
                    validationGeometry = Geometry.Combine(sourceGeometry, validationGeometry, GeometryCombineMode.Union, null);
                }

                dcrea.Collides = collides;
                dcrea.Center = dcrea.Center;

                ShowCollideValidationPath(validationGeometry);

            }
            else
            {
                dcrea.Center = svi_source.ActualCenter;

                RemoveCollideValidationPath();
            }

            this.RaiseEvent(dcrea);
        }

        private void OnScatterViewItemPreviewContactUp(object sender, ContactEventArgs e)
        {
            var svi = e.OriginalSource as ScatterViewItem;
            if (svi == null)
                return;

            if (svi.ContactsCaptured.Any(cc => cc != e.Contact))
                return;

            var manip = svi.DataContext as IManipulableObject;

            if (manip != null)
            {
                manip.LastCenterBeforeContactsUp = svi.ActualCenter;
            }
        }

        private void RemoveCollideValidationPath()
        {
            if (IsCollideValidationDisplayed)
                this._canvas.Children.Remove(collideValidationPath);
        }

        private bool IsCollideValidationDisplayed
        {
            get
            {
                return collideValidationPath != null && this._canvas.Children.Contains(collideValidationPath);
            }
        }

        private void ShowCollideValidationPath(Geometry result)
        {
            if (IsCollidePathEnabled)
            {
                var path = new Path();
                path.Data = result;
                path.Stroke = CollidePathBrush;
                path.StrokeThickness = 3;
                _canvas.Children.Add(path);

                RemoveCollideValidationPath();
                collideValidationPath = path;
            }
        }
        #endregion

        #region Properties

        public ICollectionView Cards
        {
            get
            {
                if (_cardCollectionView == null && this.ScatterView.ItemsSource is IList)
                {
                    this._cardCollectionView = new ListCollectionView(this.ScatterView.ItemsSource as IList);
                    this._cardCollectionView.Filter = (it => it is ICard);
                }
                return _cardCollectionView;
            }
        }

        public ICollectionView Decks
        {
            get
            {
                if (this._deckCollectionView == null && this.ScatterView.ItemsSource is IList)
                {
                    this._deckCollectionView = new ListCollectionView(this.ScatterView.ItemsSource as IList);
                    this._deckCollectionView.Filter = (it => it is IDeck);
                }
                return _deckCollectionView;
            }
        }

        public IEnumerable Items
        {
            get
            {
                return this.ScatterView.ItemsSource;
            }
        }

        public ICollection<IManipulableObject> ItemsSourceCollection
        {
            get
            {
                return this.ScatterView.ItemsSource as ICollection<IManipulableObject>;
            }
        }

        [Browsable(false)]
        public ScatterView ScatterView
        {
            get
            {
                return this._boardScatterView.ScatterView;
            }
        }

        public Brush CollidePathBrush { get; set; }
        public bool IsCollidePathEnabled { get; set; }

        #endregion

        #region ManipulableObjectStateChanging

        public bool RaiseDeckCreatingEvent(IDeck deck, IEnumerable<ICard> cards)
        {
            return RaiseDeckCreatingEvent(deck, cards, new Point(double.NaN, double.NaN));
        }

        public bool RaiseDeckCreatingEvent(IDeck deck, IEnumerable<ICard> cards, Point center)
        {
            var eventArgs = new ManipulableObjectStateChangingEventArgs(Board.ManipulableObjectStateChangingEvent)
            {
                Decks = new List<IDeck>() { deck },
                Cards = cards == null ? null : cards.ToList(),
                ManipulableSource = deck,
                ManipulableObjectAction = ManipulableObjectAction.CreateDeck,
                Center = center
            };

            this.RaiseEvent(eventArgs);

            return !eventArgs.Cancel;
        }

        public bool RaiseDeckOrCardSlidedOnBoard(IDeck deck, ICard card)
        {
            var eventArgs = new ManipulableObjectStateChangingEventArgs(Board.ManipulableObjectStateChangingEvent)
            {
                ManipulableSource = deck == null ? (IManipulableObject)card : (IManipulableObject)deck,
                ManipulableObjectAction = ManipulableObjectAction.MoveCardOrDeckOnBoard,
            };

            this.RaiseEvent(eventArgs);

            return !eventArgs.Cancel;
        }

        public bool MergeDecksAndCards(IDeck remainingDeck, IEnumerable<IDeck> mergedDecks, IEnumerable<ICard> cards)
        {
            var eventArgs = new ManipulableObjectStateChangingEventArgs(Board.ManipulableObjectStateChangingEvent)
            {
                Decks = mergedDecks.ToList(),
                Cards = cards == null ? null : cards.ToList(),
                ManipulableSource = remainingDeck,
                ManipulableObjectAction = ManipulableObjectAction.MergeDecks,
            };

            this.RaiseEvent(eventArgs);

            if (!eventArgs.Cancel)
            {
                if (ItemsSourceCollection == null)
                    throw new Exception("Can't remove decks as ScatterView source is not ICollection<IManipulableObject>");

                using (remainingDeck.DeferUpdateDisposition(true, true))
                {
                    // move free cards
                    foreach (var card in cards)
                        AddCardToDeck(remainingDeck, card);

                    // move cards from decks
                    foreach (var deck in mergedDecks)
                    {
                        foreach (var card in deck.ToList())
                        {
                            RemoveCardFromDeck(deck, card);
                            AddCardToDeck(remainingDeck, card);
                        }
                    }

                    // delete decks
                    foreach (var deck in mergedDecks)
                    {
                        RemoveDeck(deck, false);
                    }
                }
            }

            return !eventArgs.Cancel;
        }

        public bool RemoveDecks(IEnumerable<IDeck> decks)
        {
            var eventArgs = new ManipulableObjectStateChangingEventArgs(Board.ManipulableObjectStateChangingEvent)
            {
                Decks = decks.ToList(),
                ManipulableObjectAction = ManipulableObjectAction.RemoveDecks,
            };

            this.RaiseEvent(eventArgs);

            if (!eventArgs.Cancel)
            {
                foreach (var deck in decks)
                    RemoveDeck(deck, false);
            }

            return !eventArgs.Cancel;
        }

        public bool RemoveDeck(IDeck deck)
        {
            return RemoveDeck(deck, true);
        }

        private bool RemoveDeck(IDeck deck, bool throwsEvent)
        {
            bool executeAdd = true;

            if (throwsEvent)
            {
                var eventArgs = new ManipulableObjectStateChangingEventArgs(Board.ManipulableObjectStateChangingEvent)
                {
                    Decks = new List<IDeck> { deck },
                    ManipulableObjectAction = ManipulableObjectAction.RemoveDecks,
                };
                this.RaiseEvent(eventArgs);

                executeAdd = !eventArgs.Cancel;
            }

            if (executeAdd)
            {
                // remove cards from deck
                if (deck.Any())
                {
                    using (deck.DeferUpdateDisposition(true, true))
                    {
                        foreach (var card in deck)
                            RemoveCardFromDeck(deck, card, false);
                    }
                }

                // then remove deck
                if (ItemsSourceCollection == null)
                    throw new Exception("Can't remove decks as ScatterView source is not ICollection<IManipulableObject>");

                ItemsSourceCollection.Remove(deck);
            }

            return executeAdd;
        }

        public bool AddCardsToDeck(IDeck deck, IEnumerable<ICard> cards)
        {
            var eventArgs = new ManipulableObjectStateChangingEventArgs(Board.ManipulableObjectStateChangingEvent)
            {
                Cards = cards.ToList(),
                ManipulableObjectAction = ManipulableObjectAction.AddCardsToDeck,
                ManipulableSource = deck
            };

            this.RaiseEvent(eventArgs);

            if (!eventArgs.Cancel)
            {
                using (deck.DeferUpdateDisposition(true, true))
                {
                    foreach (var card in cards)
                        AddCardToDeck(deck, card, false);
                }
            }

            return !eventArgs.Cancel;
        }

        public bool AddCardToDeck(IDeck deck, ICard card)
        {
            return AddCardToDeck(deck, card, true);
        }

        private bool AddCardToDeck(IDeck deck, ICard card, bool throwsEvent)
        {
            if (deck == null || card == null)
                return false;

            if (deck.Contains(card))
                return false;

            bool executeAdd = true;

            if (card.Parent != null)
            {
                // first remove it from parent
                if (card.Parent is IDeck)
                    this.RemoveCardFromDeck((IDeck)card.Parent, card);
            }

            if (throwsEvent)
            {
                var eventArgs = new ManipulableObjectStateChangingEventArgs(Board.ManipulableObjectStateChangingEvent)
                {
                    Cards = new List<ICard>() { card },
                    ManipulableObjectAction = ManipulableObjectAction.AddCardsToDeck,
                    ManipulableSource = deck
                };
                this.RaiseEvent(eventArgs);

                executeAdd = !eventArgs.Cancel;
            }

            if (executeAdd)
                deck.Add(card);

            return executeAdd;
        }

        public bool RemoveCardsFromDeck(IDeck deck, IEnumerable<ICard> cards)
        {
            var eventArgs = new ManipulableObjectStateChangingEventArgs(Board.ManipulableObjectStateChangingEvent)
            {
                Cards = cards.ToList(),
                ManipulableObjectAction = ManipulableObjectAction.RemoveCardsFromDeck,
                ManipulableSource = deck
            };

            this.RaiseEvent(eventArgs);

            if (!eventArgs.Cancel)
            {
                using (deck.DeferUpdateDisposition(true, true))
                {
                    foreach (var card in cards)
                    {
                        RemoveCardFromDeck(deck, card, false);
                    }
                }
            }

            return !eventArgs.Cancel;
        }

        public bool RemoveCardFromDeck(IDeck deck, ICard card)
        {
            return RemoveCardFromDeck(deck, card, true);
        }

        public bool RemoveCardFromDeck(IDeck deck, ICard card, bool throwsEvent)
        {
            bool executeAdd = true;

            if (card.Parent == null || !(card.Parent is IDeck))
                return false;

            if (throwsEvent)
            {
                var eventArgs = new ManipulableObjectStateChangingEventArgs(Board.ManipulableObjectStateChangingEvent)
                {
                    Cards = new List<ICard>() { card },
                    ManipulableObjectAction = ManipulableObjectAction.RemoveCardsFromDeck,
                    ManipulableSource = deck
                };
                this.RaiseEvent(eventArgs);

                executeAdd = !eventArgs.Cancel;
            }

            if (executeAdd)
                ((IDeck)card.Parent).Remove(card);

            return executeAdd;
        }

        public bool MoveCardsFromDeck(IDeck source, IDeck destination, IEnumerable<ICard> cards)
        {
            var eventArgs = new ManipulableObjectStateChangingEventArgs(Board.ManipulableObjectStateChangingEvent)
            {
                Decks = new List<IDeck>() { destination },
                Cards = cards.ToList(),
                ManipulableObjectAction = ManipulableObjectAction.MoveCardsFromDeck,
                ManipulableSource = source
            };

            this.RaiseEvent(eventArgs);

            if (!eventArgs.Cancel)
            {
                using (source.DeferUpdateDisposition(true, true))
                using (destination.DeferUpdateDisposition(true, true))
                {
                    foreach (var card in cards)
                    {
                        RemoveCardFromDeck(source, card, false);
                        AddCardToDeck(destination, card, false);
                    }
                }
            }

            return !eventArgs.Cancel;
        }

        #endregion

        public void Clear()
        {
            this._boardScatterView.ScatterView.ItemsSource = null;
            this._canvas.Children.Clear();
            this._cardCollectionView = null;
            this._deckCollectionView = null;
        }
    }
}
